#ifndef MATRIX_H_INCLUDED
#define MATRIX_H_INCLUDED

#include "GLOBAL.h"
#include "Vector.h"

// Predefine classes:
template<class T> class mat3;
template<class T> class mat4;

// Type definitions:
typedef mat3<real> mat3r;
typedef mat3<float> mat3f;
typedef mat3<double> mat3d;
typedef mat4<real> mat4r;
typedef mat4<float> mat4f;
typedef mat4<double> mat4d;

typedef const mat3r	cmat3r;
typedef const mat3f	cmat3f;
typedef const mat3d	cmat3d;
typedef const mat4r	cmat4r;
typedef const mat4f	cmat4f;
typedef const mat4d	cmat4d;

/// Template class to define 4x4 row-major matrix
template<class T> class mat3{
	public:
		T		m11, m12, m13;	///< Row one
		T		m21, m22, m23;	///< Row two
		T		m31, m32, m33;	///< Row three

	public:
      mat3();
      mat3(		T n11, T n12, T n13,
               T n21, T n22, T n23,
               T n31, T n32, T n33		);
      mat3(		const T	*data				);
      mat3(		const mat3f		&m3x3		);
      mat3(		const mat3d		&m3x3		);
      mat3(		const mat4f		&m4x4		);
      mat3(		const mat4d		&m4x4		);
      mat3(		const vec3<T>	&row0,
               const vec3<T>	&row1,
               const vec3<T>	&row2		);
      mat3(		const vec3<T>	&right,
               const vec3<T>	&up,
               const vec3<T>	&view,
					const vec3<T>	&scale = vec3<T>( (T)1, (T)1, (T)1 )	);
      mat3(		const quat<T>	&orient,
               const vec3<T>	&scale = vec3<T>( (T)1, (T)1, (T)1 )	);


		mat3<T>      operator *		( const mat3<T> &m2	) const;
		vec3<T>      operator *		( const vec3<T> &v	) const;
		vec4<T>      operator *		( const vec4<T> &v	) const;
		mat3<T>		 operator *		( const T &scalar		) const;
		mat3<T>      operator +		( const mat3<T> &m2	) const;
		mat3<T>      operator -		( const mat3<T> &m2	) const;
		mat3<T>      operator *=	( const mat3<T> &m2	) const;
		mat3<T>      operator *=	( const T &scalar		) const;
		bool         operator ==	( const mat3<T> &m2	) const;
		bool         operator !=	( const mat3<T> &m2	) const;

      void			GetArray( T *arr ) const;
      void			GetArrayCM( T *arr ) const;
      void			GetArrayRM( T *arr ) const;
      vec3<T>     GetRow( const uint16 &row ) const;
      vec3<T>     GetCol( const uint16 &col ) const;
      vec3<T>     GetEuler( );
      mat3<T>     Transposed( void ) const;

		// Scaling
		void			SetScale( const vec3<T> &v );
		void			SetScale( const T &s );
		vec3<T>		GetScale() const;
		void			Scale( const vec3<T> &v );
		void			Scale( const T &s );
		void			Scalar( const vec3<T> &v );
		void			Scalar( const T &s );

		// Rotating
		void			Rotational( const quat<T> &q );
		void			Rotational( const vec3<T> &q, const T &a );
      void			RotationalX( const T &x );
      void			RotationalY( const T &x );
      void			RotationalZ( const T &x );

		// Shearing
		void			Shearal( const vec3<T> &q );
      void			Shear( const vec3<T> &q );

		// Combined
      void			Make( const quat<T> &ori, const vec3<T> &sca );
		void			Set(		T n11, T n12, T n13,
									T n21, T n22, T n23,
									T n31, T n32, T n33		);
		void			Null();
		void			Identity();

		// Others
		vec3<T>		GetViewVector	( bool column_major = true ) const;
		vec3<T>		GetUpVector		( bool column_major = true ) const;
		vec3<T>		GetRightVector	( bool column_major = true ) const;
		void			SetViewVector	( const vec3<T> &view, 	bool column_major = true );
		void			SetUpVector		( const vec3<T> &up, 	bool column_major = true );
		void			SetRightVector	( const vec3<T> &right, bool column_major = true );
};


/// Template class to define 4x4 row-major matrix
template<class T> class mat4{
	public:
		T		m11, m12, m13, m14;	///< Row one
		T		m21, m22, m23, m24;	///< Row two
		T		m31, m32, m33, m34;	///< Row three
		T		m41, m42, m43, m44;	///< Row four

	public:
      mat4();
      mat4(		T n11, T n12, T n13, T n14,
               T n21, T n22, T n23, T n24,
               T n31, T n32, T n33, T n34,
               T n41, T n42, T n43, T n44		);
      mat4(		const T	*data		);
      mat4(		const mat3f		&m3x3		);
      mat4(		const mat3d		&m3x3		);
      mat4(		const mat4f		&m4x4		);
      mat4(		const mat4d		&m4x4		);
      mat4(		const vec4<T>	&row0,
               const vec4<T>	&row1,
               const vec4<T>	&row2,
               const vec4<T>	&row3	= vec4<T>( 0, 0, 0, (T)1 ) );
      mat4(		const vec3<T>	&right,
               const vec3<T>	&up,
               const vec3<T>	&view,
               const vec3<T>	&pos = vec3<T>( 0, 0, 0 ),
					const vec3<T>	&scale = vec3<T>( (T)1, (T)1, (T)1 )	);
      mat4(		const quat<T>	&orient,
               const vec3<T>	&scale = vec3<T>( (T)1, (T)1, (T)1 ),
               const vec3<T>	&pos = vec3<T>( 0, 0, 0 )	);


		mat4<T>      operator *		( const mat4<T> &m2	) const;
		vec3<T>      operator *		( const vec3<T> &v	) const;
		vec4<T>      operator *		( const vec4<T> &v	) const;
		mat4<T>		 operator *		( const T &scalar		) const;
		mat4<T>      operator +		( const mat4<T> &m2	) const;
		mat4<T>      operator -		( const mat4<T> &m2	) const;
		mat4<T>      operator *=	( const mat4<T> &m2	) const;
		mat4<T>      operator *=	( const T &scalar		) const;
		bool         operator ==	( const mat4<T> &m2	) const;
		void         operator  =	( const mat3<T> &m2	);
		bool         operator !=	( const mat4<T> &m2	) const;

      void			GetArray( T *arr ) const;
      void			GetArrayCM( T *arr ) const;
      void			GetArrayRM( T *arr ) const;
      vec4<T>     GetRow( const uint16 &row ) const;
      vec4<T>     GetCol( const uint16 &col ) const;
      vec3<T>     GetEuler( );
      mat4<T>     Transposed( void ) const;
		mat4<T>		Inversed( void ) const;

		// Translation
		void			SetTranslate( const vec3<T> &v );
		void			ApplyTranslation( const vec3<T> &v );
		vec3<T>		GetTranslate( ) const;
		void			Translational( const vec3<T> &v );
		void			Translational( const T &tx, const T &ty, const T &tz );

		// Scaling
		void			SetScale( const vec3<T> &v );
		void			SetScale( const T &s );
		vec3<T>		GetScale( ) const;
		void			Scale( const vec3<T> &v );
		void			Scale( const T &s );
		void			Scalar( const vec3<T> &v );
		void			Scalar( const T &s );

		// Rotating
		void			Rotational( const quat<T> &q );
		void			Rotational( const vec3<T> &q, const T &a );
      void			RotationalX( const T &x );
      void			RotationalY( const T &x );
      void			RotationalZ( const T &x );

		// Shearing
		void			Shearal( const vec3<T> &q );
      void			Shear( const vec3<T> &q );

		// Combined
      void			Make( const vec3<T> &pos, const vec3<T> &sca, const quat<T> &ori );
      void			Make( const vec3<T> &pos, const quat<T> &ori, const vec3<T> &sca );
      void			Make( const quat<T> &ori, const vec3<T> &pos, const vec3<T> &sca );
		void			Set(		T n11, T n12, T n13, T n14,
									T n21, T n22, T n23, T n24,
									T n31, T n32, T n33, T n34,
									T n41, T n42, T n43, T n44		);
		void			Null();
		mat4<T>		Identity();

		// Others
		vec3<T>		GetViewVector	( bool column_major = true ) const;
		vec3<T>		GetUpVector		( bool column_major = true ) const;
		vec3<T>		GetRightVector	( bool column_major = true ) const;
		void			SetViewVector	( const vec3<T> &view, 	bool column_major = true );
		void			SetUpVector		( const vec3<T> &up, 	bool column_major = true );
		void			SetRightVector	( const vec3<T> &right, bool column_major = true );
};

#define IDENTITY_MATRIX mat4r( 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1 )

#endif // MATRIX_H_INCLUDED
